seccamp2022L4: Uniswap ①
https://gyazo.com/a5b6ee29732439e85e200324195a4437
今回の目的
攻撃のPoCを書く上で知っておきたいDEXの知識をUniswapを題材に学ぶ
(今回は理論中心、次回は実装中心に追っていく)
Uniswap 〇〇の整理
「Uniswap」と言ったときに何を指すのか
Uniswap Protocol: Automated Market Maker(後述)のコントラクト
Uniswap Interface: Uniswap Protocolを操作できるWebインターフェース
Uniswap Labs: Uniswap ProtocolとUniswap Interfaceを開発している会社
Uniswap Governance: UNIトークンによってUniswap Protocolを管理するガバナンスシステム
Uniswap Protocolとは
EthereumでERC-20トークンを交換できるプロトコル
3つのバージョンがある: V1, V2, V3
ライセンスはV1とV2はGPLだが、V3はバンパイアアタックを回避するために最長2年間の商用利用が不可になった(参考) 補足「バンパイアタック」: DEXにおいては競合するプロトコルから流動性を奪うこと。昔SushiSwapがバンパイアタックをしかけUniswapの流動性を1週間経たずに14億ドル以上奪った。
Uniswap Protocolが従来のTradFiの取引所とどう違うのか
1. Automated Market Maker
2. Permissionless
前提: TradFiの取引所はオーダーブック方式が主流
https://gyazo.com/46c63a8b14c36f576618ba17117e3bc9
トレーダー同士の取引
指値注文: いくらでどれくらい買いたい or 売りたいという注文。
成行注文: どれくらい買いたい or 売りたいという注文。価格は指定しない。
Automated Market Maker (AMM)
https://gyazo.com/9e8cdfce7364d2988cdc673f6d667866
トレーダーとプール間での取引
UniswapではConstant Function Market MakerというタイプのAMM
プールにあるトークンA,Bの量をそれぞれ$ x,yとすると$ xy=kとなるように価格が調整される。この定数$ kをInvariantという。
V1,V2はこのイメージで良いが、V3はConcentrated Liquidityが導入されたため異なる
Permissionless
誰でもスワップや流動性提供、新しいペアを作れる
地域、資産状況、年齢などによってアクセスが制限されない
誰もアップグレード不可能(※ガバナンスによってプールへの報酬率は変更可能)
スワップ
売るERC-20トークンとその量、買うERC-20トークンを指定する(インターフェース上の話)
買うERC-20トークンの量は自動で算出される
Uniswap Interfaceではリアルタイムでおおよその予測量が表示される
マーケットインパクトが大きい場合は警告がでる
スリッページの恐れがあり、許容値を設定できる
忘れたころに実行されないように、スワップの有効期限を指定できる
スリッページ
注文が実行されるまでに起きる価格変化のこと
Ethereumは全てのトランザクションが公開されており、各トランザクションの実行は通常gas price(EIP-1559ではmax_priority_fee_per_gas)が高い順に実行される
もし大きな額の注文トランザクションが自分の注文トランザクションの前に実行された場合、想定していた量と大きく乖離する場合がある
不利な注文にならないようにスリッページの許容値を注文に設定でき、もし許容外となった場合はトランザクションが失敗し、スワップは起きない
Concentrated Liquidity
前提: V2までは0から∞までの価格帯で取引が可能だった。しかし、実際に使われる価格帯は一部分であることが一部で問題視されていた。
例えばステーブルコインのペアであるV2 DAI/USDCペアは0.99ドルから1.01ドルの間に流動性プールの総資本の0.5%しか使用していない。DAI/USDCペアの価格変動は安定しているため資本効率が悪い。
V3では流動性提供者は価格帯を限定できるようになった
流動性提供者が望めばリスクを高めより多くの取引手数料を得れる
例えば、0.99から1.01ドルまでの範囲内に全ての資本を集中できるようになった
Concentrated Liquidityで流動性提供するリスク
価格が流動性提供で設定した価格帯から外れた場合、その外れている間は手数料は得られない。元に戻れば手数料は再び得られるようになる。
価格帯が外れたとき、流動性提供者は1種類の資産しか持っていない状態になる
例えば、1 DAI = 1.02 USDCになった場合、DAIが売れてUSDCのみ所持することになる
もしペグが外れて戻らない事態になれば流動性提供者は大損する可能性がある
例えば、先日USTが崩壊したが、DAI/USTペアのプールがあったとして0.99ドルから1.01ドルの価格帯の流動性提供を行っていた場合、資産は全てUSTになってしまう。実質DAIショートUSTロングと同じ。
Concentrated Liquidityによって流動性の分布がどう変わるか
https://gyazo.com/a6a7b2c308ec94d9c1182165eb1b4e71
ティック
Concentrated Liquidityを実現するために、従来は連続的だった"価格空間"が離散的になった
1ティック = 0.01%。証券用語でbasis point (bp)と同じ。
流動性ポジションを構築するときにこのティックを使って下限と上限を指定する
通貨のスワップ時、Poolコントラクトは次のティックに到達するまで、現在のティックインターバルで利用できる全ての流動性を使用していく
Range Order
Concentrated Liquidityによって実現する擬似的な指値注文
指値注文は一般的なオーダーブックでは可能であるが一般的なAMMでは不可能
指値注文と異なるのは注文が執行したあと流動性を解除しないと注文がクローズされる可能性があること
流動性提供であるからスワップ手数料が得られ、実質的にメイカーのマイナス手数料になる
https://gyazo.com/a2020dafaeee2c543ee9658dc1561019
手数料
スワッパーが流動性提供者に支払う「スワップ手数料」
スワッパーがUniswap Governanceに支払う「プロトコル手数料」(今はゼロ)
スワップ手数料(V2以前とかなり異なる)
対象となる流動性ポジションに比例して分配される
ポジションの範囲外であれば手数料は得られない
得られた手数料が再投資されない(複利でない)
同一のトークンペアに対して異なる手数料のプールを作成できる
0.01%, 0.05%, 0.3%, 1%
0.01%はガバナンスシステムによって追加された
ステーブルコインペアであれば小さな手数料にすることができるようになる。スワッパーは安い手数料で利用できるし、Concentrated Liquidityが導入されたことで流動性提供者は手数料のパーセンテージは下がっても遜色なく手数料が得られる。
プロトコル手数料
今はゼロ
Uniswap Governanceによって有効にできる
有効にされないのはAPYが下がり他のプロトコルとの競争力が弱まるから(推測)
オラクル
プールはそのままオラクルとして機能する
デフォルトではプールは直近の価格と流動性を保存する
トランザクション手数料を支払えば誰でも最大65535ブロック分のデータを保存できるように変更できる
長期間のデータを保存できるようにすればTWAPなどのオラクルとしても使えるようになる
観測データ
code:sol(js)
struct Observation {
// the block timestamp of the observation
uint32 blockTimestamp;
// the tick accumulator, i.e. tick * time elapsed since the pool was first initialized
int56 tickCumulative;
// the seconds per liquidity, i.e. seconds elapsed / max(1, liquidity) since the pool was first initialized
uint160 secondsPerLiquidityCumulativeX128;
// whether or not the observation is initialized
bool initialized;
}
観測データを取得する関数
code:sol(js)
function observe(uint32[] calldata secondsAgos)
external
view
returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);
直近一時間のTWAPを取得したいならsecondsAgos = [3600,0]にする
直近ならsecondsAgo = [0]にする
算術平均を計算する例
secondsAgoは予め決める
code:sol(js)
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos0 = secondsAgo; (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) =
IUniswapV3Pool(pool).observe(secondsAgos);
int56 tickCumulativesDelta = tickCumulatives1 - tickCumulatives0; uint160 secondsPerLiquidityCumulativesDelta =
secondsPerLiquidityCumulativeX128s1 - secondsPerLiquidityCumulativeX128s0; arithmeticMeanTick = int24(tickCumulativesDelta / secondsAgo);
// Always round to negative infinity
if (tickCumulativesDelta < 0 && (tickCumulativesDelta % secondsAgo != 0)) arithmeticMeanTick--;
プールにはtoken0とtoken1がある
WETHがtoken0でUSDCがtoken1でarithmeticMeanTickが$ 100000だったら、1 WETHは$ 1.0001^{100000} = 22015.5USDC。
正式に対応していないトークン
トランスファー時に手数料がかかるトークン(Fee-on-transfer Token)
ラッパートークンを作るか、カスタマイズされたルーターを作る必要がある
今後もサポート予定なし
リベースするトークン(Rebasing Token)
例: Ampleforth
プールの作成やスワップはできるがリベースに対応していない
ガバナンスに使うサイト
ガバナンスフォーラム
投票インターフェース
オフチェーンでアンケート的な役割
正式な投票インターフェース
オンチェーンでガバナンスコントラクトの実行に紐づく
ガバナンスのプロセス
大きく三段階のフェーズに分かれる
Phase 1: Temperature Check
ガバナンスフォーラムに投稿し、Snapshotで投票を行う
期間は2日間
25k UNIの賛成票の定足数が必要
Phase 2: Consensus Check
ガバナンスフォーラムに投稿し、Snapshotで投票を行う
期間は5日間
50k UNIの賛成票の定足数が必要
コミュニティへの積極的
Phase 3: Governance Proposal
プロポーザルのコントラクトを書く
このコントラクトは監査されなければならない。費用はコミュニティのvaultから支出。
プロポーザルを提出するためには250万UNIが委任されていなければならない
補足「委任」: あるアドレスの議決権を任意のアドレスに託す。自分のアドレスにも可能。委任はオフチェーン、投票はオンチェーン。
ガバナンスフォーラムに過去の議論スレッドや監査レポートなど含め投稿する
ガバナンスフレームワークGovernor Bravoのproposal関数を実行し、プロポーザルをデプロイする
デプロイされたら7日間の投票期間が始まる
提案が可決されたら2日間タイムロックされたあと提案されたコードが実行される
ガバナンスへの攻撃可能性
前提: ガバナンスプロトコルはUNIの準備金への操作権を持つ
その準備金を流出させるプロポーザルを提案してもUNI自体の価格が下がるため攻撃のコストのほうが高くなる可能性が高い
万が一、プロポーザルが可決された場合でも、タイムロックにより2日間の避難猶予が与えられるため、プロトコルのフォークなどによってプロトコルは存続可能
フラッシュローンは使えない。投票権は前のブロックの情報を元に算出されるから。
Flash Swap
Uniswap V2で追加
いわゆるフラッシュローン
実は全てのスワップはFlash Swapでもある
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data);
このスワップ関数はペアコントラクトに実装されている
インプットとなるトークンを受け取ったかどうかを確認する前に、先にアウトプットするトークンを送っている
トランザクションにはアトミック性があるから最後に確認してもよいため
data.lengthが0に等しいならば、単にトークンをtoアドレスに送信する
data.lengthが0より大きいならば、トークンをtoに送った後、toアドレスのコントラクトに実装されているuniswapV2Call関数を呼び出す
このuniswapV2Call関数で資産を返す限り任意の操作を行える
参考文献